home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
CD ROM Paradise Collection 4
/
CD ROM Paradise Collection 4 1995 Nov.iso
/
music
/
midimod2.zip
/
MIDIMOD.C
< prev
next >
Wrap
Text File
|
1993-06-28
|
41KB
|
1,599 lines
/*
* MIDIMOD.C - Amiga Module to MIDI file converter
* (c)opyright Andrew Scott 1993
*
* Turbo C 2.0
*
* Description: Takes a .mod file and has a good go at converting it to
* a .mid file. Equivalents to certain .mod samples can be
* set to default to particular MIDI instruments. Multiple
* MIDI instrument tables should be supported. Note that .mod
* and .mid extensions are at the end of the filename.
*
* Author: Andrew Scott (Adrenalin Software)
*
* Date: 14/3/1993 ver 0.1
* 20/4/1993 ver 0.2
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h> /* don't forget to link in appropriate libraries */
#include "textwin.h" /* Simple text-windowing environment */
#include "midimod.h" /* Dialog/Info box messages and definitions */
bfile MidFile, ModFile;
char SongName[21];
samps Samples;
unsigned long PosLog[64];
int DrumChann = 9, TempoType = 1, PosI = 0;
string MidFN, ModFN;
void OutByte(bfile f, char b)
/* Post: The byte b has been written to the buffer of the file f */
{
if (f->o == BUFFSIZE) {
fwrite(f->b, 1, BUFFSIZE, f->f);
f->o = 0;
}
f->b[f->o++] = b;
}
void FlushOut(bfile f)
/* Pre: f was opened for writing */
/* Post: The file f has has its buffer flushed */
{
if (f->o > 0)
fwrite(f->b, 1, f->o, f->f);
f->o = 0;
}
void CloseOut(bfile f)
/* Pre: f was opened for writing */
/* Post: The file f has been flushed and is now closed */
{
FlushOut(f);
fclose(f->f);
f->f = NULL;
}
int OpenOut(bfile f, string fn)
/* Returns: NZ if the file f has been opened for writing with the name fn */
{
if (f->f != NULL)
CloseOut(f);
f->f = fopen(fn, "wb");
f->o = 0;
return f->f != NULL;
}
unsigned long Beatle(bfile f)
/* Returns: bfile-tell (btell=beatle). The offset from the start */
{
return ftell(f->f) + f->o;
}
unsigned char InByte(bfile f)
/* Pre: f was opened for reading */
/* Returns: The next byte from the file f */
{
if (f->o == BUFFSIZE && !feof(f->f)) {
f->r = fread(f->b, 1, BUFFSIZE, f->f);
if (f->r < BUFFSIZE)
f->b[f->r] = 0;
f->o = 0;
}
if (f->o < f->r)
return f->b[f->o++];
return f->b[f->o];
}
void CloseIn(bfile f)
/* Post: The file f is now closed */
{
fclose(f->f);
f->f = NULL;
}
int OpenIn(bfile f, string fn)
/* Returns: NZ if the file f has been opened for reading with the name fn */
{
if (f->f != NULL)
CloseIn(f);
f->f = fopen(fn, "rb");
f->o = f->r = BUFFSIZE;
return f->f != NULL;
}
void Inskipp(bfile f, unsigned long n) /* Stainless-steel rat for Pres */
/* Pre: f was opened for reading */
/* Post: f's file pointer has skipped forward n bytes */
{
n += f->o;
while (n >= BUFFSIZE && !feof(f->f)) {
f->r = fread(f->b, 1, BUFFSIZE, f->f);
if (f->r < BUFFSIZE)
f->b[f->r] = 0;
n -= BUFFSIZE;
}
f->o = n; /* hmmm.. may cause an error if was eof.. X-fingers */
}
struct bpos FPos(bfile f)
/* Returns: All necessary information regarding file f's status */
{
struct bpos x;
x.d = *f;
x.p = ftell(f->f);
return x;
}
void FGoto(bfile f, struct bpos x)
/* Pre: x was the status of f previously */
/* Post: File f has had its status changed to x */
{
fseek(f->f, x.p, SEEK_SET);
*f = x.d;
}
int WriteVLQ(bfile f, unsigned long i)
/*
* Returns: # of bytes written after a variable-length-quantity equivalent
* of i has been written to the file f.
*/
{
int x = 0;
unsigned long buffer;
buffer = i & 127;
while ((i >>= 7) > 0)
buffer = ((buffer << 8) | 128) + (i & 127);
while (1) {
OutByte(f, buffer & 255);
x++;
if (buffer & 128)
buffer >>= 8;
else
return x;
}
}
string InitFile(bfile f, string deffn, string t, int inpm)
/* Returns: NULL if file is unacceptable, the filename otherwise. The
* filename corresponds to file f, type t, with default filename deffn.
* Will open an input file if inpm is NZ. deffn will be freed.
*/
{
int r = 0;
string newfn;
_IF[1] = t;
if (deffn==NULL)
*(deffn = (string) malloc(1)) = 0;
newfn = DialogBox(_IF, deffn);
free(deffn);
if (! *newfn) {
free(newfn);
return NULL;
}
if (inpm)
r = OpenIn(f, newfn);
else if (fclose(fopen(newfn, "r"))==EOF || tolower(InfoBox(_OUTE))=='y')
r = OpenOut(f, newfn);
if (!r) {
free(newfn);
return NULL;
}
return newfn;
}
int MKTest(bfile f)
/* Returns: The number of samples in the Module file f */
{
unsigned long offset;
int i = 15;
char s[4];
offset = ftell(f->f);
if (!fseek(f->f, 1080, SEEK_SET)) {
fread(s, 1, 4, f->f);
if (!memcmp(s,"M.K.",4) || !memcmp(s,"M!K!",4) || !memcmp(s,"FLT4",4))
i = 31;
else if (!memcmp(s,"FLT8",4))
i = 0;
}
fseek(f->f, offset, SEEK_SET);
return i;
}
string SimplifyName(string s)
/*
* Returns: A string similar to s, but has had any nasty headers removed
* any leading spaces or trailing spaces, and all made lower-case with
* any intermediate spaces replaced by underbar-characters
*/
{
string t, r, x;
x = strchr(s, ':');
if (x != NULL && x-s == 5 && tolower(s[0])=='s' && tolower(s[1])=='t' &&
s[2]=='-') {
r = x = (string) malloc(18);
t = s + 6;
while (*(x++) = *(t++)); /* remove soundtracker header */
} else
strcpy(r = (string) malloc(1+strlen(s)), s); /* strdup? */
for (t = r; *t == ' '; t++);
x = r;
while (*(x++) = *(t++)); /* remove leading spaces */
if (*r) {
x--;
while (*(--x) == ' ') /* remove trailing spaces */
*x = 0;
}
for (x = r; *x; x++) /* fill intermediate spaces with _ */
if (*x == ' ')
*x = '_';
t = r = (string) realloc(r, strlen(r) + 1);
for (; *t; t++)
*t = tolower(*t); /* make the lot lower-case */
return r;
}
string GetLine(FILE *f)
/* Returns: Next line from file f, NULL on error/eof */
{
string s, t;
int c;
if ((t = s = (string) malloc(MAXSTRING))==NULL) {
InfoBox(_OOME);
return NULL;
}
while ((c = fgetc(f)) != EOF && c != '\n')
*(t++) = c;
if (s == t) {
free(s);
return NULL;
}
*t = 0;
return (string) realloc(s, t-s+1);
}
int ReadModSpecs(bfile f, string n, samps s)
/*
* Returns: Z if f is not a supported Amiga Module, else n is set to
* be the name of the Module and s is set to hold sample information
*/
{
unsigned char b, c;
int i, okmodule;
string t1;
samp *t2;
for (i = 20, t1 = n; i--; *(t1++) = InByte(f));
okmodule = !*n || isprint(*n);
*t1 = 0;
c = s->n = MKTest(f);
okmodule = okmodule && c;
for (t2 = s->s; c--; t2++) {
for (i = 22, t1 = t2->n; i--; *(t1++) = InByte(f));
okmodule = okmodule && (!*(t2->n) || isprint(*(t2->n)));
*t1 = 0;
b = InByte(f);
t2->l = 256 * b + InByte(f);
if (t2->l < 4)
t2->l = 0; /* if the sample is this small, not worth processing */
b = InByte(f);
t2->v = InByte(f);
InByte(f);
InByte(f);
b = InByte(f);
if (256 * b + InByte(f) > 1 && t2->l)
t2->l = -1; /* looping: plays 'forever' */
t2->m = 0;
}
return !feof(f->f) && okmodule;
}
void ScanSamples(samps s)
/*
* Post: Sample data for ModFile has been scanned and samples in s have
* been given "reasonable" values for volume shifting and transpositions
* I hope :)
*/
{
samp *sam;
unsigned int maxpat = 0, i, j, k;
unsigned char x;
signed char c[SCANSIZE]; /* look at first SCANSIZE bytes of sample */
signed char max, min, *p;
unsigned long offset, len, lenarry[31];
long sum;
double ratio, junk;
offset = ftell(ModFile->f);
fseek(ModFile->f, 42, SEEK_SET);
for (i = 0; i < s->n; i++) { /* need to get actual lengths */
fread(&x, 1, 1, ModFile->f);
lenarry[i] = 256 * x;
fread(&x, 1, 1, ModFile->f);
lenarry[i] += x;
lenarry[i] *= 2;
fseek(ModFile->f, 28, SEEK_CUR);
}
fseek(ModFile->f, 20 + 30 * s->n, SEEK_SET);
fread(&x, 1, 1, ModFile->f);
fread(c, 1, 129, ModFile->f);
for (; x; x--)
if (c[x] > maxpat)
maxpat = c[x];
len = 1024L * (maxpat + 1) + 30 * s->n + ((s->n > 30) ? 4 : 0) + 150;
fseek(ModFile->f, len, SEEK_SET);
for (i = 0, sam = s->s; i < s->n; i++, sam++) {
sam->t[0] = 0; /* set transposition values to 0 */
sam->t[1] = 0;
sam->t[2] = 0;
sam->a[0] = 0;
if (!sam->l) {
sam->a[1] = 1; /* no sample = no volume shifting */
sam->a[2] = 1;
fseek(ModFile->f, lenarry[i], SEEK_CUR);
} else {
len = min(lenarry[i], SCANSIZE);
if (!(j = len = fread(c, 1, len, ModFile->f)))
len = 1;
fseek(ModFile->f, lenarry[i] - j, SEEK_CUR);
min = 127;
max = -128;
sum = 0;
for (p = c; j--; p++) {
sum += *p;
if (*p > max)
max = *p;
if (*p < min)
min = *p;
}
ratio = 1.0 * sum / len; /* get average .. normally ~0 */
if (fabs(max - ratio) > fabs(min - ratio))
ratio = fabs(min - ratio) / 64.0;
else
ratio = fabs(max - ratio) / 64.0;
j = k = 1;
while (k++ < 15) /* now find decent rational approx. */
if (fabs(modf(ratio * k, &junk)-.5) > fabs(modf(ratio * j, &junk)-.5))
j = k;
if (!(sam->a[1] = ratio * j + 0.5)) {
sam->a[1] = 1; /* if really small scaling needed.. */
sam->a[2] = 16;
} else
sam->a[2] = j;
}
}
fseek(ModFile->f, offset, SEEK_SET);
}
int SetDefaults(samps s, string fn)
/*
* Returns: NZ if the samples in s have been sucessfully allocated default
* values corresponding to definitions in the DEF_MAPFILE file, and from
* a .mm file corresponding to the filename fn
*/
{
FILE *f;
char i, m[MAXSTRING];
int d, e[6], v;
samp *sam;
string n, t;
bfile mmf;
t = strchr(strcpy(m, fn), '.');
if (t==NULL) {
i = strlen(m);
m[i] = '.';
} else
i = t-m;
m[++i] = 'm';
m[++i] = 'm';
m[++i] = 0;
mmf->f = NULL;
if (OpenIn(mmf, m)) {
for (i = s->n, sam = s->s; i--; sam++) {
sam->m = InByte(mmf);
sam->t[0] = (signed char) InByte(mmf);
sam->t[1] = (signed char) InByte(mmf);
sam->t[2] = (signed char) InByte(mmf);
sam->a[0] = (signed char) InByte(mmf);
sam->a[1] = (signed char) InByte(mmf);
sam->a[2] = (signed char) InByte(mmf);
}
CloseIn(mmf);
} else
ScanSamples(s);
if ((f = fopen(DEF_MAPFILE, "rt"))==NULL) {
_NOFIL[3] = DEF_MAPFILE;
InfoBox(_NOFIL);
return 0;
}
i = s->n;
for (sam = s->s; i--; sam++)
if (sam->l) {
n = SimplifyName(sam->n);
t = GetLine(f);
e[3] = 0; /* These are the default volume constants */
e[4] = 1;
e[5] = 1;
sscanf(t, "%s %d %d %d %d %d %d %d", m, &d, &e[0], &e[1], &e[2],
&e[3], &e[4], &e[5]);
if ((v = strcmp(m, n))>0) {
rewind(f);
free(t);
t = GetLine(f);
e[3] = 0;
e[4] = 1;
e[5] = 1;
sscanf(t, "%s %d %d %d %d %d %d %d", m, &d, &e[0], &e[1], &e[2],
&e[3], &e[4], &e[5]);
v = strcmp(m, n);
}
free(t);
while (v < 0 && (t = GetLine(f)) != NULL) {
e[3] = 0;
e[4] = 1;
e[5] = 1;
sscanf(t, "%s %d %d %d %d %d %d %d", m, &d, &e[0], &e[1], &e[2],
&e[3], &e[4], &e[5]);
free(t);
v = strcmp(m, n);
}
if (!v) {
sam->m = d;
sam->t[0] = e[0];
sam->t[1] = e[1];
sam->t[2] = e[2];
sam->a[0] = e[3];
sam->a[1] = e[4];
sam->a[2] = e[5];
}
free(n);
}
fclose(f);
return 1;
}
void SaveDefaults(samps s, string fn)
/*
* Post: The samples attributes in s have been written to a .mm file
* corresponding to the filename fn
*/
{
char m[MAXSTRING];
string t;
char i;
bfile mmf;
samp *sam;
t = strchr(strcpy(m, fn), '.');
if (t==NULL) {
i = strlen(m);
m[i] = '.';
} else
i = t-m;
m[++i] = 'm';
m[++i] = 'm';
m[++i] = 0;
mmf->f = NULL;
if (!OpenOut(mmf, m))
return;
for (i = s->n, sam = s->s; i--; sam++) {
OutByte(mmf, sam->m);
OutByte(mmf, sam->t[0]);
OutByte(mmf, sam->t[1]);
OutByte(mmf, sam->t[2]);
OutByte(mmf, sam->a[0]);
OutByte(mmf, sam->a[1]);
OutByte(mmf, sam->a[2]);
}
CloseOut(mmf);
}
void NullArryFree(string *sp)
/* Post: The NULL-terminated array sp is gone */
{
string *t;
t = sp;
while (*t != NULL)
free(*(t++));
free(sp);
}
int Instrument()
/* Returns: MIDI instrument selected from file DEF_INSFILE */
{
static int w = 0, doff = -1;
static string *sp = NULL;
int c;
if (sp == NULL) {
FILE *f;
string *t, s;
int x, i = 1;
if ((f = fopen(DEF_INSFILE, "rt"))==NULL) {
_NOFIL[3] = DEF_INSFILE;
InfoBox(_NOFIL);
return -1;
}
if ((t = sp = (string *) malloc(257 * sizeof(string)))==NULL) {
InfoBox(_OOME);
return -1;
}
while ((s = GetLine(f)) != NULL)
if (x = strlen(s)) { /* ignore blank lines */
i++;
if (x > w)
w = x;
*(t++) = s;
if (doff < 0 && *s=='D') /* take note of first drum position */
sscanf(s, "D%d ", &doff);
}
*t = NULL;
sp = (string *) realloc(sp, i * sizeof(string)); /* i <= 257 */
fclose(f);
}
c = ScrollChoice("MIDI Instruments", sp, w);
if (c>127)
c += doff;
return c;
}
string MIDIVal(samp *sam)
/* Returns: a string representing the MIDI instrument *sam */
{
string s;
s = (string) malloc(5);
if (sam->m < 128)
sprintf(s, "%4d", sam->m);
else
sprintf(s, "D%3d", sam->m - 128);
return s;
}
string TransVal(samp *sam)
/* Returns: a string representing the transpose amount of *sam */
{
string s;
s = (string) malloc(15);
if (!sam->t[1])
sprintf(s, " %4d", sam->t[0]);
else if (!sam->t[2])
sprintf(s, " %4d,%4d", sam->t[0], sam->t[1]);
else
sprintf(s, "%4d,%4d,%4d", sam->t[0], sam->t[1], sam->t[2]);
return s;
}
string VolumeVal(samp *sam)
/* Returns: a string representing the volume shifting formula for *sam */
{
string s;
s = (string) malloc(17);
if (sam->a[0] < 0)
sprintf(s, "(? -%3d)*%3d/%3d", abs(sam->a[0]), sam->a[1], sam->a[2]);
else
sprintf(s, "(? +%3d)*%3d/%3d", sam->a[0], sam->a[1], sam->a[2]);
return s;
}
string NullVal(samp *sam)
/* Returns: an empty string */
{
string s;
*(s = (string) malloc(1)) = 0;
return s;
}
int Sample(samps s, string (*MIDIVal)(samp *x), int w)
/* Returns: Sample selected from list of samples, -1 on error */
{
string *sp, *t, p;
samp *sam;
int i;
if ((t = sp = (string *) malloc((s->n + 1) * sizeof(string)))==NULL) {
InfoBox(_OOME);
return -1;
}
for (i = s->n, sam = s->s; i--; sam++) {
*t = (string) malloc(25 + w);
p = MIDIVal(sam);
sprintf(*t, "%c%-22s %s", (sam->l) ? '*' : ' ', sam->n, p);
free(p);
t++;
}
*t = NULL;
i = ScrollChoice("Select Sample", sp, 24 + w);
NullArryFree(sp);
return i;
}
int ChooseChannels(samps s)
/*
* Returns: The number of different channels needed to play instruments. If
* that number is not greater than 16, upto 16 channels will be allocated
* to the samples.
*/
{
unsigned char c, d = 0, i[128], m, n, numchan;
samp *sam1, *sam2;
n = s->n;
sam1 = s->s;
memset(i, 0, 128);
for (n = s->n, sam1 = s->s; n--; sam1++) {
sam1->c = -1;
if (sam1->l)
if (sam1->m > 127) {
d = 1;
sam1->c = DrumChann;
} else
i[sam1->m] = 1;
else
sam1->m = 0;
}
for (numchan = d, n = 128; n--; numchan += i[n]);
if (numchan > 16)
return numchan;
/* Ok.. now we must go through and set channels appropriately */
m = s->n;
sam1 = s->s;
c = 0;
while (m--) {
if (sam1->c < 0) {
sam1->c = c;
n = m;
sam2 = sam1 + 1;
while (n--) {
if (sam2->c < 0)
if (sam2->m == sam1->m || ! sam2->l)
sam2->c = c;
sam2++;
}
if (++c == DrumChann && d)
c++;
}
sam1++;
}
return numchan;
}
void MapSamples(samps s)
/* Post: The samples s have been allocated appropriate instruments, we hope */
{
int i, j;
do
if ((i = Sample(s, MIDIVal, 4))>=0 && (j = Instrument())>=0)
s->s[i].m = j;
while (i>=0);
}
void SaveSamp(samp sam)
/* Post: The sample sam has been updated in the DEF_MAPFILE file */
{
FILE *f1, *f2;
string s, n;
int v, d;
char m[MAXSTRING];
if (!sam.l)
return;
if ((f1 = fopen(DEF_MAPFILE, "rt")) == NULL) {
_NOFIL[3] = DEF_MAPFILE;
InfoBox(_NOFIL);
return;
}
f2 = fopen("temp.$$$", "wt");
n = SimplifyName(sam.n);
v = 1;
while (v > 0 && (s = GetLine(f1)) != NULL) {
sscanf(s, "%s %d", m, &d);
if ((v = strcmp(n, m)) <= 0) {
fprintf(f2, "%s %d %d %d %d %d %d %d\n", n, sam.m, sam.t[0], sam.t[1],
sam.t[2], sam.a[0], sam.a[1], sam.a[2]);
free(n);
n = NULL;
if (v)
fprintf(f2, "%s\n", s);
} else
fprintf(f2, "%s\n", s);
free(s);
}
while ((s = GetLine(f1))!=NULL) {
fprintf(f2, "%s\n", s);
free(s);
}
if (n != NULL) {
fprintf(f2, "%s %d %d %d %d %d %d %d\n", n, sam.m, sam.t[0], sam.t[1],
sam.t[2], sam.a[0], sam.a[1], sam.a[2]);
free(n);
}
fclose(f2);
fclose(f1);
if (unlink(DEF_MAPFILE)<0 || rename("temp.$$$", DEF_MAPFILE)<0)
InfoBox(_NOSAV);
}
void SaveSamples(samps s)
/* Post: The desired sample attributes of s have been saved to disk */
{
int i;
do
if ((i = Sample(s, NullVal, 0))>=0)
SaveSamp(s->s[i]);
while (i>=0);
}
void Transpositions(samps s)
/* Post: All necessary transpositions have been applied to the samples s */
{
int i;
do
if ((i = Sample(s, TransVal, 14))>=0) {
char s1[41];
string s2;
samp *sam;
sam = s->s + i;
if (!sam->t[1])
sprintf(s1, "%d", sam->t[0]);
else if (!sam->t[2])
sprintf(s1, "%d,%d", sam->t[0], sam->t[1]);
else
sprintf(s1, "%d,%d,%d", sam->t[0], sam->t[1], sam->t[2]);
s2 = DialogBox(_TRAQ, s1);
sam->t[1] = 0;
sam->t[2] = 0;
sscanf(s2, "%d,%d,%d", &sam->t[0], &sam->t[1], &sam->t[2]);
free(s2);
if ((sam->t[0] || sam->t[1]) && sam->m > 127)
InfoBox(_TRANWRN);
if (sam->t[0]<-128 || sam->t[0]>127 || sam->t[1]<-128 ||
sam->t[1]>127 || sam->t[2]<-128 || sam->t[2]>127) {
InfoBox(_TRANE);
sam->t[0] = 0;
sam->t[1] = 0;
sam->t[2] = 0;
}
}
while (i>=0);
}
void VolumeShifts(samps s)
/* Post: All volume shifting formulae have been defined for the samples s */
{
int i;
do
if ((i = Sample(s, VolumeVal, 16))>=0) {
char s1[41];
string s2;
samp *sam;
sam = s->s + i;
sprintf(s1, "%d,%d,%d", sam->a[0], sam->a[1], sam->a[2]);
s2 = DialogBox(_VSHQ, s1);
if (sscanf(s2, "%d,%d,%d", &sam->a[0], &sam->a[1], &sam->a[2])!=3 ||
sam->a[0] > 127 || sam->a[0] < -128 || sam->a[1] > 127 ||
sam->a[1] < 0 || sam->a[2] > 127 || sam->a[2] < 1) {
InfoBox(_VSHE);
sam->a[0] = 0;
sam->a[1] = 1;
sam->a[2] = 1;
}
free(s2);
}
while (i>=0);
}
void SetDrumChannel()
/* Post: Drum channel is set to desired value - 1 */
{
string s;
char s2[3];
sprintf(s2, "%d", DrumChann+1);
s = DialogBox(_DCQ, s2);
if (sscanf(s, "%d", &DrumChann))
DrumChann--; /* Drum channel is stored as value - 1 */
}
void SetTempoType()
/* Post: Tempo interpretting will either be extended or normal */
{
char inp;
while ((inp = InfoBox(TempoType ? _TTQ1 : _TTQ0)) != '0' && inp != '1');
TempoType = inp - '0';
}
unsigned char NoteValue(unsigned int n)
/* Returns: MIDI note equivalent of MOD period-lengths */
{
static unsigned int t[72] = {
1712, 1616, 1525, 1440, 1357, 1281, 1209, 1141, 1077, 1017, 961, 907,
856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453,
428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226,
214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113,
107, 101, 95, 90, 85, 80, 76, 71, 67, 64, 60, 57,
53, 50, 48, 45, 42, 40, 38, 36, 34, 32, 30, 28
};
signed char a = 0, m = 35, b = 71;
if (!n)
return 0;
for (; b-a > 1; m = (a + b) / 2) /* binary search */
if (t[m] < n)
b = m;
else
a = m;
if (n - t[b] < t[a] - n) /* choose closest value */
return b + 36;
return a + 36;
}
unsigned long NoteLength(unsigned char n, unsigned int l, unsigned int b)
/* Returns: # of ticks to play note length l at pitch n, b beats/min */
{
static float t[84] = {
3.200e-3, 3.020e-3, 2.851e-3, 2.691e-3, 2.540e-3, 2.397e-3,
2.263e-3, 2.136e-3, 2.016e-3, 1.903e-3, 1.796e-3, 1.695e-3,
1.600e-3, 1.510e-3, 1.425e-3, 1.345e-3, 1.270e-3, 1.197e-3,
1.131e-3, 1.068e-3, 1.008e-3, 9.514e-4, 8.980e-4, 8.476e-4,
8.000e-4, 7.551e-4, 7.127e-4, 6.727e-4, 6.350e-4, 5.993e-4,
5.657e-4, 5.339e-4, 5.040e-4, 4.757e-4, 4.490e-4, 4.238e-4,
4.000e-4, 3.775e-4, 3.564e-4, 3.364e-4, 3.175e-4, 2.997e-4,
2.828e-4, 2.670e-4, 2.520e-4, 2.378e-4, 2.245e-4, 2.119e-4,
2.000e-4, 1.888e-4, 1.782e-4, 1.682e-4, 1.587e-4, 1.498e-4,
1.414e-4, 1.335e-4, 1.260e-4, 1.189e-4, 1.122e-4, 1.059e-4,
1.000e-4, 9.439e-5, 8.909e-5, 8.409e-5, 7.937e-5, 7.492e-5,
7.071e-5, 6.674e-5, 6.300e-5, 5.946e-5, 5.612e-5, 5.297e-5,
5.000e-5, 4.719e-5, 4.454e-5, 4.204e-5, 3.969e-5, 3.746e-5,
3.536e-5, 3.337e-5, 3.150e-5, 2.973e-5, 2.806e-5, 2.649e-5
}; /* multipliers for each pitch: 12th roots of 2 apart */
return t[n - 36] * b * l; /* better not slide out of this range :( */
}
void WriteHeader(bfile mf, unsigned char n)
/* Post: The MIDI header has been written to mf, with #tracks = n */
{
static unsigned char MIDIH[14] = {
77, 84, 104, 100, 0, 0, 0, 6, 0, 1, 0, -1, 0, 192
};
int i = 0;
MIDIH[11] = n+1;
while (i < 14)
OutByte(mf, MIDIH[i++]);
}
unsigned int Trk0Info(bfile mf, string s)
/*
* Returns: the number of bytes written as track 0 so far, given mf as the
* output MIDI file. s is the name of the tune.
*/
{
static unsigned char TRK0I[63] = {
0, 255, 2, 42, 70, 105, 108, 101, 32, 67, 111, 112, 121, 114, 105, 103,
104, 116, 32, 40, 99, 41, 32, 49 ,57, 57, 51, 32, 65, 100, 114, 101, 110,
97, 108, 105, 110, 32, 83, 111, 102, 116, 119, 97, 114, 101,
0, 255, 88, 4, 3, 2, 24, 8,
0, 255, 89, 2, 0, 0,
0, 255, 3
}; /* standard header + copyright message */
unsigned int i = 0;
while (i < 63)
OutByte(mf, TRK0I[i++]);
i = 64 + strlen(s);
OutByte(mf, strlen(s));
while (*s)
OutByte(mf, *(s++));
return i;
}
void AddToLog(unsigned long a, unsigned long b)
/* Post: a (file position) and b (number) have been added to the log */
{
PosLog[PosI++] = b;
PosLog[PosI++] = a;
}
void WriteLog(FILE *f)
/* Post: PosLog has been written into file f */
{
unsigned long x, y;
unsigned char c[4];
if (!PosI)
return;
x = ftell(f);
while (PosI) {
fseek(f, PosLog[--PosI], SEEK_SET);
y = PosLog[--PosI];
c[3] = y & 255;
y >>= 8;
c[2] = y & 255;
y >>= 8;
c[1] = y & 255;
y >>= 8;
c[0] = y;
fwrite(c, 1, 4, f);
}
fseek(f, x, SEEK_SET);
}
unsigned char RestrictVol(int v)
/* Returns: A volume in the range 0..127 */
{
if (v < 0)
return 0;
else
return (v > 127) ? 127 : v;
}
void ConvertMOD(bfile f1, bfile f2, string tn, samps smp)
/*
* Post: The Amiga MODfile f1 has been converted and written to MIDI file f2,
* using instrument mappings smp. Tune has a name tn.
*/
{
unsigned char b, length, pattern[128];
string n, t;
unsigned int i, j, k, tempdone = 0;
samp *sam;
struct bpos p1, p2;
p1 = FPos(f1);
length = InByte(f1);
InByte(f1);
for (i = 0; i < 128; pattern[i++] = InByte(f1));
Inskipp(f1, (smp->n > 15) ? 4 : 0);
WriteHeader(f2, smp->n);
p2 = FPos(f1);
for (i = 0, sam = smp->s - 1; i <= smp->n; sam++, i++) {
unsigned long cnt, inst, timer, delay[4];
unsigned char c;
OutByte(f2, 77);
OutByte(f2, 84);
OutByte(f2, 114);
OutByte(f2, 107);
inst = Beatle(f2); /* store this position so can set AFTERWARDS - hehe */
OutByte(f2, 0);
OutByte(f2, 0);
OutByte(f2, 0);
OutByte(f2, 0);
if (!i)
cnt = Trk0Info(f2, tn);
else {
OutByte(f2, 0);
OutByte(f2, 255);
OutByte(f2, 3);
b = strlen(n = sam->n);
OutByte(f2, b);
cnt = 7 + b;
while (b--)
OutByte(f2, *(n++));
c = sam->c;
OutByte(f2, 0);
OutByte(f2, 0xC0 + c); /* set channel */
OutByte(f2, (sam->m > 127) ? 126 : sam->m);
}
timer = 0;
if (sam->l || !i) {
unsigned int bpm, ticks, l, effect, h;
unsigned char sampnum, lastsam[4] = {0}, vol[4];
signed char patbreak;
unsigned long x, pause;
int note, lastslide, slideto;
int n[4][48][2]; /* note data for a song position */
int lastn[4]; /* last note on a particular channel */
sprintf(_CNVPOS[2]+7, "%d", i); /* some compilers hate this */
DrawBox(_CNVPOS);
memset(lastn, 0, 4 * sizeof(int));
memset(vol, 0, 4);
memset(delay, 0, 4 * sizeof(long));
bpm = 125;
ticks = 6;
lastslide = slideto = 0;
patbreak = 0;
for (h = 48; h--; )
for (k = 4; k--; )
n[k][h][0] = 0;
for (l = 0; l < length; l++) {
if (pattern[l]<=pattern[l-1] || !l) {
FGoto(f1, p2);
x = (unsigned) 1024 * pattern[l];
} else
x = (unsigned) 1024 * (pattern[l]-pattern[l-1]-1);
Inskipp(f1, x);
j = 64;
if (patbreak>0)
patbreak = 1-patbreak;
while (j--) {
pause = 0;
if (patbreak)
Inskipp(f1, 16);
else
for (k = 0; k < 4; k++) {
n[k][0][1] = sam->v;
sampnum = InByte(f1);
note = ((sampnum & 15) << 8) + InByte(f1);
effect = InByte(f1);
sampnum = (sampnum & ~15) + (effect >> 4);
if (!i)
note = 0;
if ((note || sampnum) && delay[k]) { /* stop old note */
cnt += 3 + WriteVLQ(f2, timer);
timer = 0;
OutByte(f2, 0x80 + c); /* note off */
OutByte(f2, ENOTE(lastn[k], 0));
OutByte(f2, RestrictVol(EVOL(vol[k])));
if (sam->t[1]) {
cnt += 4;
OutByte(f2, 0);
OutByte(f2, 0x80 + c);
OutByte(f2, ENOTE(lastn[k], 1));
OutByte(f2, RestrictVol(EVOL(vol[k])));
if (sam->t[2]) {
cnt += 4;
OutByte(f2, 0);
OutByte(f2, 0x80 + c);
OutByte(f2, ENOTE(lastn[k], 2));
OutByte(f2, RestrictVol(EVOL(vol[k])));
}
}
delay[k] = 0;
}
if (!note && sampnum == i) /* check "defaults" */
note = lastn[k];
else if (!sampnum)
if (lastsam[k] == i)
sampnum = i;
else
note = 0;
else {
if (sampnum != i)
note = 0;
lastsam[k] = sampnum;
}
n[k][0][0] = note;
effect = ((effect & 15) << 8) + InByte(f1);
switch (effect & 0xF00) {
case 0x000: { /* arpeggio */
int nv;
if (!i || !effect || sam->m > 127)
break;
if (!note)
if (!delay[k])
break;
else {
nv = NoteValue(lastn[k]);
n[k][47][0] = lastn[k];
n[k][47][1] = vol[k];
if (effect & 0x0F0)
n[k][16][0] = -(nv + ((effect & 0x0F0) >> 4));
n[k][16][1] = vol[k];
if (effect & 0x00F)
n[k][32][0] = -(nv + (effect & 0x00F));
n[k][32][1] = vol[k];
}
else {
nv = NoteValue(note);
n[k][47][0] = note;
n[k][47][1] = sam->v;
if (effect & 0x0F0)
n[k][16][0] = -(nv + ((effect & 0x0F0) >> 4));
n[k][16][1] = sam->v;
if (effect & 0x00F)
n[k][32][0] = -(nv + (effect & 0x00F));
n[k][32][1] = sam->v;
}
break;
}
case 0x300: /* slide to */
if (!note && !slideto || note==lastn[k] || sam->m > 127)
break;
if (effect & 0x0FF)
lastslide = effect & 0x0FF;
else
lastslide = abs(lastslide);
if (note)
slideto = note;
if (slideto > lastn[k]) {
n[k][0][0] = lastn[k] + lastslide*(ticks-1);
if (n[k][0][0] > 856)
n[k][0][0] = 856;
if (n[k][0][0] > slideto)
n[k][0][0] = slideto;
} else {
n[k][0][0] = lastn[k] - lastslide*(ticks-1);
if (n[k][0][0] < 113)
n[k][0][0] = 113;
if (n[k][0][0] < slideto)
n[k][0][0] = slideto;
}
n[k][0][1] = vol[k];
break;
case 0x100: /* slide up */
case 0x200: /* slide down */
if (!(effect & 0x0FF) || sam->m > 127)
break;
if (effect & 0x200)
lastslide = effect & 0x0FF;
else
lastslide = -(effect & 0x0FF);
if (!note)
if (!delay[k])
break;
else {
n[k][0][0] = lastn[k] + lastslide;
n[k][0][1] = vol[k];
}
else
n[k][0][0] += lastslide;
if (n[k][0][0] > 856)
n[k][0][0] = 856;
else if (n[k][0][0] < 113)
n[k][0][0] = 113;
break;
case 0x400: /* vibrato */
case 0x700: /* tremolo */
/* ignore these effects.. not convertable */
break;
case 0x500: /* slide to & volume slide */
if ((note || slideto) && note!=lastn[k] && sam->m < 128) {
if (note)
slideto = note;
if (slideto > lastn[k]) {
n[k][0][0] = lastn[k] + lastslide*(ticks-1);
if (n[k][0][0] > 856)
n[k][0][0] = 856;
if (n[k][0][0] > slideto)
n[k][0][0] = slideto;
} else {
n[k][0][0] = lastn[k] - lastslide*(ticks-1);
if (n[k][0][0] < 113)
n[k][0][0] = 113;
if (n[k][0][0] < slideto)
n[k][0][0] = slideto;
}
} else
n[k][0][0] = 0;
note = 0;
case 0x600: /* vibrato & volume slide */
case 0xA00: { /* volume slide */
int v;
if (!note)
v = vol[k];
else
v = sam->v;
v += (ticks-1)*(effect & 0x0F0); /* can't really slide */
v -= (ticks-1)*(effect & 0x00F);
if (v > 127)
v = 127;
else if (v < 0)
v = 0;
n[k][0][1] = v;
break;
}
case 0x900: /* set offset: pretend it's retrigger */
if ((!n[k][0][0] || !sampnum) && delay[k]) {
n[k][0][0] = lastn[k];
n[k][0][1] = vol[k];
}
break;
case 0xB00: /* position jump: ignore, but break anyway */
patbreak = 1;
break;
case 0xD00: /* pattern break */
patbreak = 1 + 10 * (effect & 0x0F0) + (effect & 0x00F);
break;
case 0xC00: /* set volume */
n[k][0][1] = effect & 0x0FF;
break;
case 0xF00: { /* set tempo */
int temp;
temp = effect & 0x0FF;
if (!temp)
temp = 1;
if (temp < 32) {
ticks = temp;
if (TempoType) { /* Tempos act strangely so .. */
bpm = 750 / temp;
x = 80000 * temp;
}
} else {
bpm = temp;
x = 60000000 / temp;
}
if (i)
break; /* only write tempo on track 0 */
cnt += 6 + WriteVLQ(f2, timer);
timer = 0;
OutByte(f2, 255); /* meta-event */
OutByte(f2, 81); /* set tempo */
OutByte(f2, 3);
OutByte(f2, x >> 16);
OutByte(f2, (x >> 8) & 0xFF);
OutByte(f2, x & 0xFF);
tempdone = 1;
break;
}
case 0xE00: /* extended effects */
switch (effect & 0x0F0) {
case 0x010: /* fine slide up */
if (!(effect & 0x00F) || sam->m > 127)
break;
if (!note)
if (!delay[k])
break;
else {
n[k][h][0] = lastn[k] + (effect & 0x00F);
n[k][h][1] = vol[k];
}
else
n[k][h][0] += effect & 0x00F;
break;
case 0x020: /* fine slide down */
if (!(effect & 0x00F) || sam->m > 127)
break;
if (!note)
if (!delay[k])
break;
else {
n[k][h][0] = lastn[k] - (effect & 0x00F);
n[k][h][1] = vol[k];
}
else
n[k][h][0] -= effect & 0x00F;
break;
case 0x000: /* set filter on/off */
case 0x030: /* glissando on/off */
case 0x040: /* set vibrato wave */
case 0x050: /* set finetune */
case 0x060: /* pattern loop */
case 0x070: /* set tremolo wave */
case 0x080: /* un-used */
case 0x0F0: /* invert loop */
/* can't do these in MIDI.. ignore */
break;
case 0x0A0: /* fine volume slide up */
case 0x0B0: { /* fine volume slide down */
int v;
v = sam->v;
if (effect & 0x0A0)
v += effect & 0x00F;
else
v -= effect & 0x00F;
if (v<0)
v = 0;
else if (v>127)
v = 127;
n[k][0][1] = v;
break;
}
case 0x090: { /* retrigger sample */
int a, b, c;
if (!note && !delay[k] || !(effect & 0x00F))
break;
a = effect & 0x00F;
if (!(ticks / a))
break;
if (!note) {
n[k][0][0] = lastn[k];
n[k][0][1] = vol[k];
}
c = 0;
b = 1;
a *= 48;
while (c < 48) {
n[k][c][0] = note;
n[k][c][1] = n[k][0][1];
c = b * a / ticks;
b++;
}
break;
}
case 0x0C0: { /* cut sample */
int a;
if (!note && !delay[k])
break;
a = 48 * (effect & 0x00F) / ticks;
if (a > 47)
break;
if (note)
n[k][a][0] = note;
else
n[k][a][0] = lastn[k];
n[k][a][1] = 0;
break;
}
case 0x0D0: { /* delay sample */
int a;
if (!note || !(effect & 0x00F))
break;
a = 48 * (effect & 0x00F) / ticks;
n[k][0][0] = 0;
if (a > 47)
break;
n[k][a][0] = note;
n[k][a][1] = n[k][a][0];
break;
}
case 0x0E0: /* pattern pause */
pause = 48 * (effect & 0x00F);
break;
}
break;
/* else dunno what it does.. disbelieve it ;) */
}
}
for (h = 0; h<48; h++) {
for (k = 0; k < 4; k++)
if (n[k][h][0]) {
if (delay[k]) { /* turn off old note on same channel */
cnt += 3 + WriteVLQ(f2, timer);
timer = 0;
OutByte(f2, 0x80 + c); /* note off */
OutByte(f2, ENOTE(lastn[k], 0));
OutByte(f2, RestrictVol(EVOL(vol[k])));
if (sam->t[1]) {
cnt += 4;
OutByte(f2, 0);
OutByte(f2, 0x80 + c);
OutByte(f2, ENOTE(lastn[k], 1));
OutByte(f2, RestrictVol(EVOL(vol[k])));
if (sam->t[2]) {
cnt += 4;
OutByte(f2, 0);
OutByte(f2, 0x80 + c);
OutByte(f2, ENOTE(lastn[k], 2));
OutByte(f2, RestrictVol(EVOL(vol[k])));
}
}
delay[k] = 0;
}
lastn[k] = n[k][h][0];
n[k][h][0] = 0;
vol[k] = n[k][h][1];
cnt += 3 + WriteVLQ(f2, timer);
timer = 0;
OutByte(f2, 0x90 + c); /* note on */
OutByte(f2, ENOTE(lastn[k], 0));
OutByte(f2, RestrictVol(EVOL(vol[k])));
if (sam->t[1]) {
cnt += 4;
OutByte(f2, 0);
OutByte(f2, 0x90 + c);
OutByte(f2, ENOTE(lastn[k], 1));
OutByte(f2, RestrictVol(EVOL(vol[k])));
if (sam->t[2]) {
cnt += 4;
OutByte(f2, 0);
OutByte(f2, 0x90 + c);
OutByte(f2, ENOTE(lastn[k], 2));
OutByte(f2, RestrictVol(EVOL(vol[k])));
}
}
delay[k] = NoteLength(ANOTE(lastn[k]), sam->l, bpm);
} else if (delay[k]==1) {
delay[k] = 0;
cnt += 3 + WriteVLQ(f2, timer);
timer = 0;
OutByte(f2, 0x80 + c); /* note off */
OutByte(f2, ENOTE(lastn[k], 0));
OutByte(f2, RestrictVol(EVOL(vol[k])));
if (sam->t[1]) {
cnt += 4;
OutByte(f2, 0);
OutByte(f2, 0x80 + c);
OutByte(f2, ENOTE(lastn[k], 1));
OutByte(f2, RestrictVol(EVOL(vol[k])));
if (sam->t[2]) {
cnt += 4;
OutByte(f2, 0);
OutByte(f2, 0x80 + c);
OutByte(f2, ENOTE(lastn[k], 2));
OutByte(f2, RestrictVol(EVOL(vol[k])));
}
}
} else if (delay[k]>0)
delay[k]--;
timer++;
}
timer += pause;
if (patbreak<0)
patbreak++;
else if (patbreak>0) {
patbreak = 1 - patbreak;
while (j) {
j--;
Inskipp(f1, 16); /* 16 bytes / song position */
}
}
}
}
for (k = 0; k < 4; k++)
if (delay[k]) {
cnt += 3 + WriteVLQ(f2, timer);
timer = 0;
OutByte(f2, 0x80 + c); /* note off */
OutByte(f2, ENOTE(lastn[k], 0));
OutByte(f2, RestrictVol(EVOL(vol[k])));
if (sam->t[1]) {
cnt += 4;
OutByte(f2, 0);
OutByte(f2, 0x80 + c);
OutByte(f2, ENOTE(lastn[k], 1));
OutByte(f2, RestrictVol(EVOL(vol[k])));
if (sam->t[2]) {
cnt += 4;
OutByte(f2, 0);
OutByte(f2, 0x80 + c);
OutByte(f2, ENOTE(lastn[k], 2));
OutByte(f2, RestrictVol(EVOL(vol[k])));
}
}
}
}
if (!i && !tempdone) {
cnt += 7;
OutByte(f2, 0); /* give the default 128 bpm if none done yet */
OutByte(f2, 255);
OutByte(f2, 81);
OutByte(f2, 3);
OutByte(f2, 7);
OutByte(f2, 39);
OutByte(f2, 14);
}
cnt += 3 + WriteVLQ(f2, timer);
OutByte(f2, 255);
OutByte(f2, 47);
OutByte(f2, 0);
AddToLog(inst, cnt); /* process this later */
FGoto(f1, p2);
}
ClearWin();
FlushOut(f2);
WriteLog(f2->f);
FGoto(f1, p1);
}
int main(int argc, char *argv[])
{
int c;
MainWindow("MIDIMOD - Amiga Noise/Sound/Protracker to MIDI converter",
4, "File", 'f', "Samples", 's', "Options", 'o', "Help", 'h');
SetMenu(0, 6, "Dest. midi",'d',0, "Source mod",'s',1, "------------",-1,-1,
"Convert",'c',2, "------------",-1,-1, "Quit",'q',99);
SetMenu(1, 5, "Map samples",'m',3, "Transposing",'t',6, "Volume shift",'v',9,
"------------",-1,-1, "Save info",'s',4);
SetMenu(2, 2, "Drum channel",'d',7, "Tempo type",'t',8);
SetMenu(3, 1, "About",'a',5);
MidFile->f = ModFile->f = NULL;
MidFN = ModFN = NULL;
if (argc>2) {
strcpy(MidFN = (string) malloc(strlen(argv[2])+1), argv[2]);
if (fclose(fopen(MidFN, "r"))==EOF || tolower(InfoBox(_OUTE))=='y')
OpenOut(MidFile, MidFN);
if (MidFile->f==NULL) {
free(MidFN);
MidFN = NULL;
}
}
if (argc>1) {
strcpy(ModFN = (string) malloc(strlen(argv[1])+1), argv[1]);
if (OpenIn(ModFile, ModFN))
if (!ReadModSpecs(ModFile, SongName, Samples))
InfoBox(_MODE);
else if (!SetDefaults(Samples, ModFN))
CloseIn(ModFile);
if (ModFile->f==NULL) {
free(ModFN);
ModFN = NULL;
}
}
do {
c = Choice();
switch(c) {
case 0:
MidFN = InitFile(MidFile, MidFN, "MIDI file below.", 0);
break;
case 1:
if (ModFile->f != NULL)
SaveDefaults(Samples, ModFN); /* make sure defaults are saved */
ModFN = InitFile(ModFile, ModFN, "MOD file below.", 1);
if (ModFN != NULL)
if (!ReadModSpecs(ModFile, SongName, Samples))
InfoBox(_MODE);
else if (!SetDefaults(Samples, ModFN))
CloseIn(ModFile);
break;
case 2:
if (MidFile->f != NULL && ModFile->f != NULL)
if (ChooseChannels(Samples) <= 16)
ConvertMOD(ModFile, MidFile, SongName, Samples);
else
InfoBox(_TMC);
else
InfoBox(_CNVE);
break;
case 3:
if (ModFile->f != NULL)
MapSamples(Samples);
else
InfoBox(_MODM);
break;
case 4:
if (ModFile->f != NULL)
SaveSamples(Samples);
else
InfoBox(_MODM);
break;
case 5:
InfoBox(_ABOUT);
break;
case 6:
if (ModFile->f != NULL)
Transpositions(Samples);
else
InfoBox(_MODM);
break;
case 7:
SetDrumChannel();
break;
case 8:
SetTempoType();
break;
case 9:
if (ModFile->f != NULL)
VolumeShifts(Samples);
else
InfoBox(_MODM);
break;
}
} while (c != 99);
if (ModFile->f != NULL) {
CloseIn(ModFile);
SaveDefaults(Samples, ModFN);
}
free(ModFN);
if (MidFile->f != NULL)
CloseOut(MidFile);
free(MidFN);
EndWindows();
return 0;
}